<?php
/*! \brief
 * This is abstract class of Parameter
 * This is class main purpose use to cooperate with WFSql object
 * EX:
 * $objParameter = WF::getParameter();
 * $objParameter->appendSelect('widget_id');
 * $objParameter->appendFrom('WIDGET');
 * $objParameter->andWhereEqual('widget_id',1);
 * $objParameter->orderByDesc('widget_id');
 * 
 * $data = WF::getWidgetPoolFactory()->getDataObject()->querySelectAllByParameter($objParameter);
 * $data = WF::getWidgetPoolFactory()->getDataObject()->querySelectRowByParameter($objParameter);
 * $data = WF::getWidgetPoolFactory()->getDataObject()->querySelectCountByParameter($objParameter);
 * $data = WF::getWidgetPoolFactory()->getDataObject()->querySelectUpdateByParameter($objParameter);
 * $data = WF::getWidgetPoolFactory()->getDataObject()->querySelectDeleteByParameter($objParameter);
 * \author William Su
 */
abstract class AParameter
{
    public function __construct($mix = null){
        if(null !== $mix){
            $this->setByArray($mix);
        }
    }
    protected $instance;
    /**
     *\brief Description
     * - This method will get all property $objParameter include private/protected/public property
     */
    public function getObjectVars(){
        return get_object_vars($this);
    }
    /**
     *\brief Description
     * - This method will get $value by $key from $instance property of $objParameter.
     */
    protected function getParameterValue($key){
        if(isset($this->instance[$key])){
            return $this->instance[$key];
        }else{
            return null;
        }
    }
    /**
     *\brief Description
     * - This method will set $key => $value to $instance property,
     * - to central control property of $objParameter
     * EX : 
     * $assocData['widget_id'] = '1';
     * $assocData['widget_name'] = 'widget_name1';
     * =>will set to objParameter
     * $objParameter->instance['widget_id'] = '1';
     * $objParameter->instance['widget_name'] = 'widget_name1';
     */
    protected function setParameterValue($key, $value){
        $this->instance[$key] = $value;
    }
    /**
     *\brief Description
     * - get upper camel case
     * - EX : widget_id = WidgetId
     * - EX : WIDGET_ID = WidgetId
     * @return string
     */
    public function getUpperCamelCase($str){
        $str = strtolower($str);
        $isUnderscore = false;
        if(false !== strpos($str, '_')){
            $str = str_replace('_', ' ', $str);
            $isUnderscore = true;
        }
        $str = strtolower($str);
        $str = ucwords($str);
        if(true === $isUnderscore){
            $str = str_replace(' ', '', $str);
        }
        return $str;
    }
    /**
     *\brief Description
     * - This method will set assocData to $objParameter by setter method but doesn\'t return self
     * EX : 
     * $assocData['widget_id'] = '1';
     * $assocData['widget_name'] = 'widget_name1';
     * =>will set to objParameter
     * $objParameter->widget_id = '1';
     * $objParameter->widget_name = 'widget_name1';
     * @return WFParameter
     */
    public function setByArray($mix){
        if(is_array($mix) && $mix){
            foreach($mix as $key => $value){
                $key = $this->getUpperCamelCase($key); //user UpperCamelCase as key
                $this->setParameterValue($key, $value);
            }
        }elseif(is_object($mix) && ($mix instanceof AParameter)){
            $this->setByArray($mix->toArray());
        }
    }
    /**
     *\brief Description
     * - This method will set assocData to $objParameter by setter method and return self
     * EX : 
     * $assocData['widget_id'] = '1';
     * $assocData['widget_name'] = 'widget_name1';
     * =>will set to objParameter
     * $objParameter->widget_id = '1';
     * $objParameter->widget_name = 'widget_name1';
     * @return WFParameter
     */
    public function getSetByArray($mix){
        $ret = $this; //default
        if(is_array($mix) && $mix){
            foreach($mix as $key => $value){
                $key = $this->getUpperCamelCase($key); //user UpperCamelCase as key
                $this->setParameterValue($key, $value);
            }
        }elseif(is_object($mix) && TRUE == is_subclass_of($mix, 'AParameter')){
            $ret = $mix;
        }
        return $ret;
    }
    protected $arrFrom;
    /**
     *\brief Description
     * - EX : $objParameter->appendFrom('WIDGET') => SELECT FROM WIDGET / DELETE FROM WIDGET / UPDATE WIDGET
     * - EX : $objParameter->appendFrom('WIDGET','w') => SELECT FROM WIDGET AS w
     */
    public function appendFrom($strFrom, $asTableName = null){
        if(null !== $asTableName){
            $this->arrFrom[$asTableName] = $strFrom;
        }else{
            $this->arrFrom[$strFrom] = $strFrom;
        }
    }
    /**
     *\brief Description
     * - This method get final SQL Statement of FROM
     * - FROM WIDGET,WIDGET_CATEGORY
     */
    public function getFrom(){
        $ret = null;
        if(! (is_array($this->arrFrom) && $this->arrFrom)){
            return $ret;
        }
        $arrTable = array();
        foreach($this->arrFrom as $asTableName => $strTableName){
            if($strTableName != $asTableName){
                $arrTable[] = $strTableName . ' AS ' . $asTableName;
            }else{
                $arrTable[] = $strTableName;
            }
        }
        $ret = implode(',', $arrTable);
        return $ret;
    }
    protected $arrSelect;
    /**
     *\brief Description
     * - This method check if SQL Statement is GROUP BY Statement or not?
     */
    private function isGroupByStatement(){
        $ret = false;
        if(count($this->arrSelect) == '1' && $this->getIsGroupCount()){
            $ret = true;
        }
        return $ret;
    }
    /**
     *\brief Description
     * - This method get final SQL Statement of SELECT
     * - SELECT w.widget_id,w.widget_name
     */
    public function getSelect(){
        if(is_array($this->arrSelect) && $this->arrSelect){
            if($this->isGroupByStatement()){
                $ret = implode(',', $this->arrSelect);
            }else{
                $arrSelectTmp = array();
                foreach($this->arrSelect as $strTableAlias => $csvSelect){
                    if(false === is_numeric($strTableAlias)){
                        $arrSelectTmp[] = $this->handleSelectTableAlias($csvSelect, $strTableAlias);
                    }else{
                        $arrSelectTmp[] = $csvSelect;
                    }
                }
                $ret = implode(',', $arrSelectTmp);
            }
        }else{
            $ret = '*';
        }
        return $ret;
    }
    /**
     *\brief Description
     * - This method will handle if parameter has define $strTableAlias
     * - EX : if $objParameter->appendSelect('widget_id,widget_name','w') => SELECT w.widget_id,w.widget_name FROM WIDGET AS w
     */
    private function handleSelectTableAlias($csvSelect, $strTableAlias){
        $ret = '';
        $arrSelect = explode(',', $csvSelect);
        if(is_array($arrSelect) && $arrSelect){
            $tmp = array();
            foreach($arrSelect as $strSelect){
                $tmp[] = $strTableAlias . '.' . $strSelect;
            }
            $ret = implode(',', $tmp);
        }
        return $ret;
    }
    /**
     *\brief Description
     * - EX : $objParameter->appendSelect('widget_id,widget_name') => SELECT widget_id,widget_name FROM WIDGET
     * - EX : $objParameter->appendFrom('widget_id,widget_name','w') => SELECT w.widget_id,w.widget_name FROM WIDGET AS w
     */
    public function appendSelect($csvSelect, $strTableAlias = null){
        if(null !== $strTableAlias){
            $this->arrSelect[$strTableAlias] = $csvSelect;
        }else{
            $this->arrSelect[] = $csvSelect;
        }
    }
    protected $arrWhere = array();
    /**
     *\brief Description
     * - EX : $objParameter->appendWhere('widget_id','1') => AND widget_id='1'
     */
    public function appendWhere($strWhere, $logic = 'AND'){
        $this->arrWhere[$logic][] = $strWhere;
    }
    /**
     *\brief Description
     * - EX : $objParameter->andWhereEqual('widget_id','1') 
     * => AND widget_id=:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function andWhereEqual($strColName, $strValue = null){
        $this->getWhereByCondition('=', 'AND', $strColName, $strValue);
    }
    /**
     *\brief Description
     * - EX : $objParameter->orWhereEqual('widget_id','1') 
     * => OR widget_id=:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function orWhereEqual($strColName, $strValue = null){
        $this->getWhereByCondition('=', 'OR', $strColName, $strValue);
    }
    /**
     *\brief Description
     * - EX : $objParameter->andWhereGte('widget_id','1') 
     * => AND widget_id>=:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function andWhereGte($strColName, $strValue){
        $this->getWhereByCondition('>=', 'AND', $strColName, $strValue);
    }
    /**
     *\brief Description
     * - EX : $objParameter->orWhereGte('widget_id','1') 
     * => OR widget_id>=:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function orWhereGte($strColName, $strValue){
        $this->getWhereByCondition('>=', 'OR', $strColName, $strValue);
    }
    /**
     *\brief Description
     * - EX : $objParameter->andWhereGt('widget_id','1') 
     * => AND widget_id>:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function andWhereGt($strColName, $strValue){
        $this->getWhereByCondition('>', 'AND', $strColName, $strValue);
    }
    /**
     *\brief Description
     * - EX : $objParameter->orWhereGt('widget_id','1') 
     * => OR widget_id>:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function orWhereGt($strColName, $strValue){
        $this->getWhereByCondition('>', 'OR', $strColName, $strValue);
    }
    /**
     *\brief Description
     * - EX : $objParameter->andWhereLte('widget_id','1') 
     * => AND widget_id<=:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function andWhereLte($strColName, $strValue){
        $this->getWhereByCondition('<=', 'AND', $strColName, $strValue);
    }
    /**
     *\brief Description
     * - EX : $objParameter->orWhereLte('widget_id','1') 
     * => OR widget_id<=:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function orWhereLte($strColName, $strValue){
        $this->getWhereByCondition('<=', 'OR', $strColName, $strValue);
    }
    /**
     *\brief Description
     * - EX : $objParameter->andWhereLt('widget_id','1') 
     * => AND widget_id<:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function andWhereLt($strColName, $strValue){
        $this->getWhereByCondition('<', 'AND', $strColName, $strValue);
    }
    /**
     *\brief Description
     * - EX : $objParameter->orWhereLt('widget_id','1') 
     * => OR widget_id<:widget_id
     * => $assocDataPDO[':widget_id'] = '1';
     */
    public function orWhereLt($strColName, $strValue){
        $this->getWhereByCondition('<', 'OR', $strColName, $strValue);
    }
    /**
     *\brief Description
     * @param $symbolGteOrLteOrOther >,>=,=,<,<=
     * @param $strAndOr AND,OR
     */
    protected function getWhereByCondition($symbolGteOrLteOrOther, $strAndOr, $strColName, $strValue = null){
        if(null !== $strValue){
            //avoid below condition
            //same column assign two where condition
            //$objParameter->andWhereLte("mcm.min_support_version_float", $numberVersion);
            // min_support_version_float must > 0
            //$objParameter->andWhereGt("mcm.min_support_version_float", 0);
            if(! isset($this->instance['strColNameColonIndex'][$strColName])){
                $this->instance['strColNameColonIndex'][$strColName] = 0;
            }
            ++$this->instance['strColNameColonIndex'][$strColName];
            $strColNameColon = $strColName . $this->instance['strColNameColonIndex'][$strColName];
            if(false !== strpos($strColName, '.')){
                $strColNameColon = str_replace('.', '_', $strColName . $this->instance['strColNameColonIndex'][$strColName]);
            }
            $this->arrWhere[$strAndOr][] = $strColName . $symbolGteOrLteOrOther . ':' . $strColNameColon;
            $this->instance['getAssocDataPDO'][':' . $strColNameColon] = $strValue;
        }else{ //if $strValue is empty, mean join SQL Statement, EX: WIDGET.widget_id=WIDGET_CATEGETORY.widget_id
            $this->arrWhere[$strAndOr][] = $strColName;
        }
    }
    /**
     *\brief Description
     * - EX : $objParameter->andWhereIn('widget_id',array('1','2')) 
     * => AND widget_id in (:whereIn1,:whereIn2)
     * => $assocDataPDO[':whereIn1'] = '1';
     * => $assocDataPDO[':whereIn2'] = '2';
     */
    public function andWhereIn($strColName, $arrValue){
        if(is_array($arrValue) && $arrValue){
            $arrColNameTmp = array();
            foreach($arrValue as $value){
                $arrColNameTmp[':whereIn' . $value] = ':whereIn' . $value;
                $this->instance['getAssocDataPDO'][':whereIn' . $value] = $value;
            }
            $this->arrWhere['AND'][] = $strColName . ' IN (' . implode(',', $arrColNameTmp) . ')';
        }
        /*else{
            $strErrMsg = __CLASS__.'::'.__FUNCTION__.'()=>$arrValue is empty!';
            throw new WFConfigParameterException($strErrMsg);
        }*/
    }
    /**
     *\brief Description
     * - This method will return $assocDataPDO
     */
    public function getAssocDataPDO(){
        $ret = $this->instance['getAssocDataPDO'];
        return $ret;
    }
    /**
     *\brief Description
     * - This method will return PDO SQL statement part of WHERE as below:
     * WHERE widget_id=:widget_id AND widget_name=:widget_name
     */
    public function getWherePDO(){
        $ret = '';
        if(is_array($this->arrWhere) && $this->arrWhere){
            $index = 1;
            foreach($this->arrWhere as $logic => $tmp){
                foreach($tmp as $strWhere){
                    if($index == 1){
                        $ret .= $strWhere;
                    }else{
                        $ret .= ' ' . $logic . ' ' . $strWhere;
                    }
                    $index++;
                }
            }
        }
        return $ret ? $ret : null;
    }
    protected $strOrderBy;
    /**
     *\brief Description
     * - EX : $objParameter->setOrderBy('widget_id DESC') => ORDER BY widget_id DESC
     */
    public function setOrderBy($orderBy){
        $this->strOrderBy = $orderBy;
    }
    /**
     *\brief Description
     * - EX : if $objParameter->setOrderBy('widget_id DESC') => $objParameter->getOrderBy('widget_id DESC') = widget_id DESC
     */
    public function getOrderBy(){
        return $this->strOrderBy;
    }
    protected $strPrimaryKey;
    /**
     *\brief Description
     * - Set the primary key of SQL Statement
     * - EX : $objParameter->setPrimaryKey('widget_id') => SELECT COUNT('widget_id')
     */
    public function setPrimaryKey($strPrimaryKey){
        $this->strPrimaryKey = $strPrimaryKey;
    }
    /**
     *\brief Description
     * - Get the primary key of SQL Statement
     */
    public function getPrimaryKey(){
        return $this->strPrimaryKey;
    }
    protected $assocData;
    /**
     *\brief Description
     * - EX : 
     * $objParameter->setAssocData('widget_id','1');
     * $objParameter->setAssocData('widget_name','widget_name1');
     * This method will set $assocDataPDO  for $sqlvalues of WFDatabaseObject::query($sql,$sqlvalues)
     * => $assocDataPDO[':widget_id'] = '1';
     * => $assocDataPDO[':widget_name'] = 'widget_name1';
     */
    public function setAssocData($assocData){
        $this->assocData = $assocData;
    }
    /**
     *\brief Description
     * - EX : 
     * This method will get $assocDataPDO for $sqlvalues of WFDatabaseObject::query($sql,$sqlvalues)
     * => $assocDataPDO[':widget_id'] = '1';
     * => $assocDataPDO[':widget_name'] = 'widget_name1';
     */
    public function getAssocData(){
        return $this->assocData;
    }
    protected $strGroupBy;
    /**
     *\brief Description
     * - EX : $objParameter->setGroupBy('category_id') => GROUP BY category_id
     */
    public function setGroupBy($strGroupBy){
        $this->strGroupBy = $strGroupBy;
        $strColumnCount = null !== $this->getPrimaryKey() ? $this->getPrimaryKey() : '*';
        $this->appendSelect('COUNT(' . $strColumnCount . ') as GroupCount');
        // set GroupCount Status
        $this->setIsGroupCount(true);
    }
    /**
     *\brief Description
     * - EX : if $objParameter->setGroupBy('category_id') => $objParameter->getGroupBy() = category_id
     */
    public function getGroupBy(){
        return $this->strGroupBy;
    }
    /**
     *\brief Description
     * Set a flag if the SQL Statement is a GROUP BY statement
     * @param $bool true or false
     */
    public function setIsGroupCount($bool){
        $this->setParameterValue(substr(__FUNCTION__, 3), $bool);
    }
    /**
     *\brief Description
     * Get SQL Statement check is the SQL Statement is a GROUP BY statement or not?
     * @return $bool true or false
     */
    public function getIsGroupCount(){
        return $this->getParameterValue(substr(__FUNCTION__, 3));
    }
    protected $strHaving;
    /**
     *\brief Description
     * - EX : $objParameter->setHaving('category_id > 1') => HAVING category_id > 1
     */
    public function setHaving($strHaving){
        $this->strHaving = $strHaving;
    }
    /**
     *\brief Description
     * - EX : if $objParameter->setHaving('category_id > 1') => $objParameter->getHaving() = category_id > 1
     */
    public function getHaving(){
        return $this->strHaving;
    }
    /**
     *\brief Description
     * - EX : $objParameter->setLimit(0,10) => LIMIT 0,10
     * - EX : $objParameter->setLimit(10) => LIMIT 10
     */
    public function setLimit($intOffsetOrLimit, $intLimit = null){
        if(null !== $intLimit){
            $this->strLimit = $intOffsetOrLimit . ',' . $intLimit;
        }else{
            $this->strLimit = $intOffsetOrLimit;
        }
    }
    protected $strLimit;
    /**
     *\brief Description
     * - Getter of SQL Statement LIMIT
     * - EX : 
     * if $objParameter->setLimit(0,10) => $objParameter->getLimit() = 0,10
     * if $objParameter->setLimit(10) => $objParameter->getLimit() = 0,10
     */
    public function getLimit(){
        return $this->strLimit;
    }
    /**
     *\brief Description
     * - Getter of SQL Statement LIMIT
     * - EX : 
     * if $objParameter->setLimit(0,10) => $objParameter->getLimitStart() = 0
     * if $objParameter->setLimit(10) => $objParameter->getLimitStart() = 0
     */
    public function getLimitStart(){
        list ($start, $end) = explode(',', $this->getLimit());
        return $start;
    }
    /**
     *\brief Description
     * - Getter of SQL Statement LIMIT
     * - EX : 
     * if $objParameter->setLimit(0,10) => $objParameter->getLimitEnd() = 10
     * if $objParameter->setLimit(10) => $objParameter->getLimitEnd() = 10
     */
    public function getLimitEnd(){
        list ($start, $end) = explode(',', $this->getLimit());
        return $end ? $end : null;
    }
    /**
     *\brief Description
     * Query Type setter
     * @param string $strQueryType SelectAll/SelectCount/SelectRow/SelectOne/Update/Delete/Insert
     */
    public function setQueryType($strQueryType){
        $this->setParameterValue(substr(__FUNCTION__, 3), $strQueryType);
    }
    /**
     *\brief Description
     * - Getter of Query Type
     * @return String SelectAll/SelectCount/SelectRow/SelectOne/Update/Delete/Insert
     */
    public function getQueryType(){
        return $this->getParameterValue(substr(__FUNCTION__, 3));
    }
}